package bancosys.tec.rtm.impl.diagnostician;

import static bancosys.tec.rtm.diagnostician.AbstractDiagnostician.JMX_DEFAULT_CACHE_DURATION_IN_SECONDS;
import static bancosys.tec.rtm.diagnostician.AbstractDiagnostician.JMX_NAMESPACE;
import static bancosys.tec.rtm.impl.diagnostician.MemoryUsageCalculatorDiagnostician.NAME;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.util.Iterator;
import java.util.List;

import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;

import bancosys.tec.rtm.diagnostician.AbstractDiagnostician;
import bancosys.tec.rtm.diagnostician.DiagnosisBean;

/**
 * Diagnosticador que calcula quanto de memoria esta sendo consumida no momento.
 * 
 * @author Evaristo
 * @version $Id:$ <b>Created on: </b> 01/03/2007
 */
@ManagedResource(description = NAME, objectName = JMX_NAMESPACE + ":name=memory", currencyTimeLimit = JMX_DEFAULT_CACHE_DURATION_IN_SECONDS)
public class MemoryUsageCalculatorDiagnostician extends AbstractDiagnostician {

    public static final String NAME = "Uso de Memória";

    private static final float NUMBER_1024 = 1024;

    private static final String KEY_USED_MEMORY = "usedMemory";

    private static final String KEY_FREE_MEMORY = "freeMemory";

    private static final String KEY_TOTAL_MEMORY = "totalMemory";

    private static final String KEY_MAX_MEMORY = "maxMemory";

    private boolean executeGarbageCollector;

    /**
     * Construtor default. Não será executado o garbage collector antes do calculo do uso de memória.
     */
    public MemoryUsageCalculatorDiagnostician() {
        this.executeGarbageCollector = false;
    }

    /**
     * Construtor que recebe como parametro um boolean, que no caso de true executa o garbage collector antes de calcular a memória, caso
     * false não executa.
     * 
     * @param executeGarbageCollector <code>true</code> executa o garbage collector antes de calcular a memória, caso false não executa.
     */
    public MemoryUsageCalculatorDiagnostician(boolean executeGarbageCollector) {
        this.executeGarbageCollector = executeGarbageCollector;
    }

    /**
     * {@inheritDoc}
     */
    public String getName() {
        return NAME;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean fillDiagnosisBean(DiagnosisBean bean) throws Exception {
        if (this.executeGarbageCollector) {
            System.gc();
        }
        float maxMemory = Runtime.getRuntime().maxMemory() / (NUMBER_1024 * NUMBER_1024);
        float totalMemory = Runtime.getRuntime().totalMemory() / (NUMBER_1024 * NUMBER_1024);
        float freeMemory = Runtime.getRuntime().freeMemory() / (NUMBER_1024 * NUMBER_1024);
        float usedMemory = totalMemory - freeMemory;

        bean.putValue(KEY_MAX_MEMORY, Runtime.getRuntime().maxMemory());
        bean.putValue(KEY_TOTAL_MEMORY, Runtime.getRuntime().totalMemory());
        bean.putValue(KEY_FREE_MEMORY, Runtime.getRuntime().freeMemory());
        bean.putValue(KEY_USED_MEMORY, Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());

        bean.addDescription("Números totais de uso da Memória");
        bean.addDescription("  Máximo de Memória que pode ser usada: " + maxMemory + "M");
        bean.addDescription("  Total de Memória alocada para ser usada: " + totalMemory + "M");
        bean.addDescription("  Memória Livre para ser usada: " + freeMemory + "M");

        bean.addDescription("  Memória sendo usada: " + usedMemory + "M");
        bean.addDescription("");

        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();

        bean.addDescription("Números segregados de uso da Memória Heap");
        bean.addDescription("  Init = " + memoryBean.getHeapMemoryUsage().getInit() / (NUMBER_1024 * NUMBER_1024) + " M");
        bean.addDescription("  Used = " + memoryBean.getHeapMemoryUsage().getUsed() / (NUMBER_1024 * NUMBER_1024) + " M");
        bean.addDescription("  Committed = " + memoryBean.getHeapMemoryUsage().getCommitted() / (NUMBER_1024 * NUMBER_1024) + " M");
        bean.addDescription("  Max = " + memoryBean.getHeapMemoryUsage().getMax() / (NUMBER_1024 * NUMBER_1024) + " M");
        bean.addDescription("");
        bean.addDescription("Números segregados de uso da Memória Não-Heap");
        bean.addDescription("  Init = " + memoryBean.getNonHeapMemoryUsage().getInit() / (NUMBER_1024 * NUMBER_1024) + " M");
        bean.addDescription("  Used = " + memoryBean.getNonHeapMemoryUsage().getUsed() / (NUMBER_1024 * NUMBER_1024) + " M");
        bean.addDescription("  Committed = " + memoryBean.getNonHeapMemoryUsage().getCommitted() / (NUMBER_1024 * NUMBER_1024) + " M");
        bean.addDescription("  Max = " + memoryBean.getNonHeapMemoryUsage().getMax() / (NUMBER_1024 * NUMBER_1024) + " M");

        bean.addDescription("");
        List<MemoryPoolMXBean> memoryPoolMXBeanBeans = ManagementFactory.getMemoryPoolMXBeans();
        bean.addDescription("Pools de Memória");

        Iterator<MemoryPoolMXBean> iteratorMemoryPoolMXBeans = memoryPoolMXBeanBeans.iterator();
        while (iteratorMemoryPoolMXBeans.hasNext()) {
            bean.addDescription("");
            MemoryPoolMXBean memoryPoolMXBean = iteratorMemoryPoolMXBeans.next();
            bean.addDescription("  " + memoryPoolMXBean.getName());
            bean.addDescription("    Init = " + memoryPoolMXBean.getUsage().getInit() / (NUMBER_1024 * NUMBER_1024) + " M");
            bean.addDescription("    Used = " + memoryPoolMXBean.getUsage().getUsed() / (NUMBER_1024 * NUMBER_1024) + " M");
            bean.addDescription("    Committed = " + memoryPoolMXBean.getUsage().getCommitted() / (NUMBER_1024 * NUMBER_1024) + " M");
            bean.addDescription("    Max = " + memoryPoolMXBean.getUsage().getMax() / (NUMBER_1024 * NUMBER_1024) + " M");
            bean.addDescription("    Tipo = " + memoryPoolMXBean.getType());
            bean.addDescription("");
            bean.addDescription("      Peak Usage");
            bean.addDescription("        Init = " + memoryPoolMXBean.getPeakUsage().getInit() / (NUMBER_1024 * NUMBER_1024) + " M");
            bean.addDescription("        Used = " + memoryPoolMXBean.getPeakUsage().getUsed() / (NUMBER_1024 * NUMBER_1024) + " M");
            bean.addDescription("        Committed = " + memoryPoolMXBean.getPeakUsage().getCommitted() / (NUMBER_1024 * NUMBER_1024)
                    + " M");
            bean.addDescription("        Max = " + memoryPoolMXBean.getPeakUsage().getMax() / (NUMBER_1024 * NUMBER_1024) + " M");
        }
        return true;
    }

    /**
     * @return used memory.
     */
    @ManagedAttribute(description = KEY_USED_MEMORY)
    public long getUsedMemory() {
        return (Long) getValue(KEY_USED_MEMORY);
    }

    /**
     * @return free memory.
     */
    @ManagedAttribute(description = KEY_FREE_MEMORY)
    public long getFreeMemory() {
        return (Long) getValue(KEY_FREE_MEMORY);
    }

    /**
     * @return total memory.
     */
    @ManagedAttribute(description = KEY_TOTAL_MEMORY)
    public long getTotalMemory() {
        return (Long) getValue(KEY_TOTAL_MEMORY);
    }

    /**
     * @return max memory.
     */
    @ManagedAttribute(description = KEY_MAX_MEMORY)
    public long getMaxMemory() {
        return (Long) getValue(KEY_MAX_MEMORY);
    }
}